Testing your APIs using Generated Postman Tests

  • Author Matthew Oliver
  • Email matthewaustinoliver@gmail.com

Theory

  • full combinatoric coverage is not usually feasible in real-world systems
  • pairwise, base choice, each choice, or any number of other testing techniques may be required
  • pairwise testing catches ~50-90% of defects - [kuhn]

Tools

We'll be leveraging the following third-party tools for our test example:

  • Postman - Chrome Extension / Packaged App
  • Newman - a NodeJS based Postman CLI

Technique

To illustrate the technique we will leverage

  • A sample REST WebAPI project
  • Python Scripts - to generate all input combinations and create our postman collections
  • Newman - to run our collections and report the output

We will be naively performing an exhaustive combinatorial test due to the small app domain.

Technique - Example API

We'll create a simple .net webapi project consisting of two routes:

  • GET /api/Examples
  • POST /api/Examples

The API will produce errors for some value combinations.

Technique - The Dataset


In [1]:
# imports
%matplotlib inline
from itertools import product
import json, matplotlib, pyodbc, pandas, uuid, copy
import matplotlib.pyplot as plt
import numpy as np

# some basic data
a = [[True,False],['Purchase','Refinance','LoanModification'],['AZ'],[1,2,3,4]]
pandas.DataFrame(a)


Out[1]:
0 1 2 3
0 True False None NaN
1 Purchase Refinance LoanModification NaN
2 AZ None None NaN
3 1 2 3 4

Technique - Combinations

To cover all combinations of will require n0 * n1 * ... nn combinations


In [2]:
len(a[0])*len(a[1])*len(a[2])*len(a[3])


Out[2]:
24

Lets see what that looks like by generating all unique combinations.


In [3]:
combinations = list(product(*a))
pandas.DataFrame(combinations)


Out[3]:
0 1 2 3
0 True Purchase AZ 1
1 True Purchase AZ 2
2 True Purchase AZ 3
3 True Purchase AZ 4
4 True Refinance AZ 1
5 True Refinance AZ 2
6 True Refinance AZ 3
7 True Refinance AZ 4
8 True LoanModification AZ 1
9 True LoanModification AZ 2
10 True LoanModification AZ 3
11 True LoanModification AZ 4
12 False Purchase AZ 1
13 False Purchase AZ 2
14 False Purchase AZ 3
15 False Purchase AZ 4
16 False Refinance AZ 1
17 False Refinance AZ 2
18 False Refinance AZ 3
19 False Refinance AZ 4
20 False LoanModification AZ 1
21 False LoanModification AZ 2
22 False LoanModification AZ 3
23 False LoanModification AZ 4

We can then convert the combinations to a JSON object which will be our data contract in this example.


In [4]:
for combination in combinations:
    valMap = {'isVal':combination[0],'loanType':combination[1],
              'state':combination[2],'policyCount':combination[3]}
    print(json.dumps(valMap, ensure_ascii=False))


{"isVal": true, "state": "AZ", "policyCount": 1, "loanType": "Purchase"}
{"isVal": true, "state": "AZ", "policyCount": 2, "loanType": "Purchase"}
{"isVal": true, "state": "AZ", "policyCount": 3, "loanType": "Purchase"}
{"isVal": true, "state": "AZ", "policyCount": 4, "loanType": "Purchase"}
{"isVal": true, "state": "AZ", "policyCount": 1, "loanType": "Refinance"}
{"isVal": true, "state": "AZ", "policyCount": 2, "loanType": "Refinance"}
{"isVal": true, "state": "AZ", "policyCount": 3, "loanType": "Refinance"}
{"isVal": true, "state": "AZ", "policyCount": 4, "loanType": "Refinance"}
{"isVal": true, "state": "AZ", "policyCount": 1, "loanType": "LoanModification"}
{"isVal": true, "state": "AZ", "policyCount": 2, "loanType": "LoanModification"}
{"isVal": true, "state": "AZ", "policyCount": 3, "loanType": "LoanModification"}
{"isVal": true, "state": "AZ", "policyCount": 4, "loanType": "LoanModification"}
{"isVal": false, "state": "AZ", "policyCount": 1, "loanType": "Purchase"}
{"isVal": false, "state": "AZ", "policyCount": 2, "loanType": "Purchase"}
{"isVal": false, "state": "AZ", "policyCount": 3, "loanType": "Purchase"}
{"isVal": false, "state": "AZ", "policyCount": 4, "loanType": "Purchase"}
{"isVal": false, "state": "AZ", "policyCount": 1, "loanType": "Refinance"}
{"isVal": false, "state": "AZ", "policyCount": 2, "loanType": "Refinance"}
{"isVal": false, "state": "AZ", "policyCount": 3, "loanType": "Refinance"}
{"isVal": false, "state": "AZ", "policyCount": 4, "loanType": "Refinance"}
{"isVal": false, "state": "AZ", "policyCount": 1, "loanType": "LoanModification"}
{"isVal": false, "state": "AZ", "policyCount": 2, "loanType": "LoanModification"}
{"isVal": false, "state": "AZ", "policyCount": 3, "loanType": "LoanModification"}
{"isVal": false, "state": "AZ", "policyCount": 4, "loanType": "LoanModification"}

Next, to be able to use our tests in postman we need to create a postman collection. First, we'll import an existing postman collection to a json object.


In [5]:
with open('CombinatorialTesting.json.postman_collection') as data_file:    
    postman_coll = json.load(data_file)
postman_coll


Out[5]:
{'description': '# Example Tests\n\nA set of generated tests for postman.',
 'folders': [{'description': '# Foldername\n\nThis holds requests',
   'id': '869deeff-47cd-7504-41dd-5b278fae7b73',
   'name': 'Folder1',
   'order': []}],
 'id': 'fb8ee997-7944-b912-f318-20084a4c57e1',
 'name': 'CombinatorialTesting',
 'order': ['9e3dcdb0-2d28-c6f8-0fcf-66dd654ffe3e'],
 'owner': 0,
 'public': False,
 'remoteLink': '',
 'requests': [{'collectionId': 'fb8ee997-7944-b912-f318-20084a4c57e1',
   'currentHelper': 'normal',
   'data': [],
   'dataMode': 'raw',
   'description': '# Tests some thing\n\nThis tests thing 1',
   'descriptionFormat': 'html',
   'headers': 'Content-Type: application/json\n',
   'helperAttributes': {},
   'id': '9e3dcdb0-2d28-c6f8-0fcf-66dd654ffe3e',
   'method': 'POST',
   'name': 'Example1',
   'pathVariables': {},
   'preRequestScript': '',
   'rawModeData': '{"loanType": "Purchase", "state": "AZ", "policyCount": 1, "isVal": true}',
   'responses': [],
   'tests': '',
   'time': 1457502365809,
   'url': '{{Url}}/Example',
   'version': 2}],
 'timestamp': 1457502197905}

Next we manipulate the template using our unique request combinations and dump it back out to the file system.


In [6]:
with open('CombinatorialTesting.json.postman_collection') as data_file:    
    postman_coll = json.load(data_file)
requestTemplate = copy.deepcopy(postman_coll['requests'][0])
idx = 0;
del(postman_coll['requests'][0])
for combination in combinations:
    valMap = {'isVal':combination[0],'loanType':combination[1],
              'state':combination[2],'policyCount':combination[3]}
    someobj = copy.deepcopy(requestTemplate)
    idx+=1
    someobj['name'] = str('Example ')+str(idx)
    valJson = json.dumps(valMap, ensure_ascii=False)
    someobj['rawModeData'] = str(valJson)
    someid = str(uuid.uuid4())
    someobj['id'] = someid
    someobj['url'] = "{{Url}}/Examples"
    someobj['method'] = "POST"
    someobj['tests'] = str('tests["Status code is 201"] = responseCode.code === 201;')
    postman_coll['order'].append(someid)
    postman_coll['requests'].append(someobj)

with open('CombinatorialTesting2.json.postman_collection', 'w') as outfile:
    json.dump(postman_coll, outfile)

RateServices RatesSpecReader Showcase